gprofiler2
is the R interface to the g:Profiler
toolset.
Like the web interface, gprofiler2 performs ORA with
g:GOSt against multiple databases simultaneously. It
supports multiple namespaces and numerous species.
2. Get ORA gene list
Now we need to filter for DEGs, and we will apply the same thresholds
as applied in day 1: adjusted P values/FDR < 0.01, and log2fold
change of 2.
degs <- data %>%
filter(FDR < 0.01 & abs(Log2FC) > 2) %>%
pull(Gene.ID)
cat("Number of genes passing FDR and fold change filter:", length(degs), "\n")
# Save the DEG gene list to disk:
write.table(degs, "DEGs.txt", row.names = FALSE, col.names = FALSE, quote = FALSE)
We have 792 genes passing our filters.
3. Get background gene list
Recall from the webinar and day 1 of the workshop that an
experimental background gene list is crucial to avoiding false positives
and minimising tissue bias.
The analysis in Degust has already removed lowly expressed genes, so
we can simply extract all genes from this data matrix as our background
gene list and save it as our ‘background’ object.
background <- data$Gene.ID
cat("Number of background genes:", length(background), "\n")
Number of background genes: 14420
# Save the background gene list to disk:
write.table(background, "background_genes.txt", row.names = FALSE, col.names = FALSE, quote = FALSE)
4. Run ORA with gost function
Before running the below code chunk, review the parameters. Observe
the similarities to what was available on the g:Profiler web interface,
for example organism, the correction method (g:Profiler’s custom
g_scs method), and domain scope.
Every R package has a user guide that outlines available parameters.
Open the gprofiler2
user guide and navigate to page detailing the gost function
(currently page 6).
Under the Usage subheading, all available parameters are
listed, along with their default settings. You can over-ride these
parameters by specifying your custom values in your R command.
The below full code shows all parameters:
ora <- gost(
degs,
organism = "hsapiens",
ordered_query = FALSE,
multi_query = FALSE,
significant = TRUE,
exclude_iea = FALSE,
measure_underrepresentation = FALSE,
evcodes = FALSE,
user_threshold = 0.05,
correction_method = "g_SCS",
domain_scope = "custom_annotated",
custom_bg = background,
numeric_ns = "",
sources = NULL,
as_short_link = FALSE,
highlight = FALSE
)
Since we are using many of the default parameters, we could shorten
this code to what is shown below. The results would be identical,
however not as transparent as far as easily identifying what parameters
were applied to a run.
Tip: you can run the command formals(gost) to print out
all parameters and their default values. This is a generic R function so
can be used on other tools outside of gprofiler2.
#ora <- gost(
# degs,
# correction_method = "g_SCS",
# domain_scope = "custom_annotated",
# custom_bg = background,
#)
formals(gost)
We have now run ORA with gprofiler2! View the to-most significant
enrichments with the R head command. Only significant
enrichments passing your specified threshold are included in the results
object we have named ora.
head(ora$result)
These are all biological process. What databases did this run ORA
against?
unique(ora$result$source)
Same as the web tool, we have enrichment results for GP (BP, CC, MF),
HP (human phenotype), HPA (human protein atlas), KEGG, MiRNA, Reactome,
Transcription Factors and WikiPathways.
5. Save the results to a file
The results can be saved to disk to simplify sharing or local viewing
outside the R environment. The output can be re-ordered to more closely
match what is produced in the web tool:
ora_reordered <- ora$result[, c("source", "term_name", "term_id", "p_value", "term_size", "query_size", "intersection_size", "effective_domain_size")]
head(ora_reordered)
write.csv(ora_reordered, "gprofiler_ORA_results.csv", row.names = FALSE)
6. Visualise the results
Manhattan plots
The gostplot function creates a Manhattan plot similar
to the one shown on the web tool. By setting
interactive=TRUE we can hover over the data points to see
enriched term details.
gostplot(ora,
capped = TRUE,
interactive = TRUE,
pal = c(`GO:MF` = "#dc3912",
`GO:BP` = "#ff9900",
`GO:CC` = "#109618",
KEGG = "#dd4477",
REAC = "#3366cc",
WP = "#0099c6",
TF = "#5574a6",
MIRNA = "#22aa99",
HPA = "#6633cc",
CORUM = "#66aa00",
HP = "#990099")
)
There are a lot of significant enrichments for GO biological
processes. Many of these are probably terms containing a large number of
genes, so not particularly informative. Since there is no direct
parameter to restrict term size to gostplot, we can filter
the ORA results before plotting.
# Filter the results for GO:BP terms with term_size <= 500
ora_filter_termsize <- ora
ora_filter_termsize$result <- ora$result %>% filter(term_size <= 500)
# Plot with gostplot using the filtered results
gostplot(ora_filter_termsize,
capped = TRUE,
interactive = TRUE,
pal = c(
`GO:MF` = "#dc3912",
`GO:BP` = "#ff9900",
`GO:CC` = "#109618",
KEGG = "#dd4477",
REAC = "#3366cc",
WP = "#0099c6",
TF = "#5574a6",
MIRNA = "#22aa99",
HPA = "#6633cc",
CORUM = "#66aa00",
HP = "#990099"
)
)
gprofiler2 includes a function for creating a publication-ready image
that can optionally highlight specific terms. We need to first produce a
plot with interactice = FALSE, save it to an object, and
then provide that plot object to the publish_gostplot
function.
# Plot with gostplot using the filtered results
plot <- gostplot(ora_filter_termsize,
capped = TRUE,
interactive = FALSE,
pal = c(
`GO:MF` = "#dc3912",
`GO:BP` = "#ff9900",
`GO:CC` = "#109618",
KEGG = "#dd4477",
REAC = "#3366cc",
WP = "#0099c6",
TF = "#5574a6",
MIRNA = "#22aa99",
HPA = "#6633cc",
CORUM = "#66aa00",
HP = "#990099"
)
)
The publish_gostplot function has a
highlight_terms parameter that enables you to highlight
specific terms on the plot, with a table showing enrichment details
below for those highlighted terms.
Let’s highlight some selected terms manually. You need to provide the
term ID not term name.
#Term IDs for 'Collagen degradation' and 'Collagen formation'
highlight <- c("REAC:R-HSA-1442490", "REAC:R-HSA-1474290")
filename <- "gostplot_filter_termsize_collagen.png"
publish_gostplot(plot,
highlight_terms = highlight,
filename,
width = 10,
height = 10 )
You can use R grepl function to search for terms with
names matching some keyword. Let’s highlight all terms related to
receptors:
# Filter terms containing "receptor" keyword and create a list of those term IDs
highlight <- ora$result %>%
filter(grepl("receptor", term_name, ignore.case = TRUE)) %>%
pull(term_id)
filename <- "gostplot_filter_termsize_receptors.png"
publish_gostplot(plot,
highlight_terms = highlight,
filename,
width = 10,
height = 10 )
Dotplots
If you wanted to plot each database separately as a traditional
dotplot, you can loop through each database and use the R package
ggplot2 to make a dotplot. We can filter the results by
database and term size:
# List of databases
dbs <- unique(ora$result$source)
# Loop through each database and create a separate plo for each
for (db in dbs) {
#db_results <- ora$result
db_results <- ora$result %>% filter(source == db, term_size >= 10, term_size <= 500)
# Create a dot plot for the current database
p <- ggplot(db_results, aes(x = reorder(term_name, -p_value), y = -log10(p_value))) +
geom_point(aes(size = term_size, color = significant)) +
labs(title = paste(db),
x = "Term",
y = "-log10(p-value)") +
theme_minimal() +
coord_flip() # Flips the coordinates for better visibility
# Print the plot
print(p)
}
For plots with a lot of enriched terms, the display within the
notebook is less than ideal. Saving the plot to an image file enables
better resolution:
# Filter for the GO:BP database
go_bp_results <- ora$result %>% filter(source == "GO:BP",term_size >= 10, term_size <= 500)
# Create the plot for GO:BP
p_go_bp <- ggplot(go_bp_results, aes(x = reorder(term_name, -p_value), y = -log10(p_value))) +
geom_point(aes(size = term_size, color = significant)) +
labs(title = "GO:BP",
x = "Term",
y = "-log10(p-value)") +
theme_minimal() +
coord_flip() # Flips the coordinates for better visibility
# Open a PDF device to save the plot as a full size A4:
pdf("go_bp_plot.pdf", width = 8.27, height = 11.69) # A4 portrait size in inches
# Print the plot to the device
print(p_go_bp)
# Close the device (this saves the plot)
dev.off()
7. Run a gost multi-query for up-regulated and down-regulated genes,
and compare the results to those generated from the full DEG gene
list
By providing more than one gene list and setting
multi_query = TRUE, results from all of the gene lists are
grouped by term IDs for easier comparison.
First, we need to re-extract our gene list so we have separate DEGs
for up-regulated and down-regulated genes.
up_degs <- data %>%
filter(FDR < 0.01 & Log2FC >= 2) %>%
pull(Gene.ID)
cat("Number of upregulated DEGs:", length(up_degs), "\n")
Number of upregulated DEGs: 577
# Save the DEG gene list to disk:
write.table(up_degs, "up_DEGs.txt", row.names = FALSE, col.names = FALSE, quote = FALSE)
down_degs <- data %>%
filter(FDR < 0.01 & Log2FC <= -2) %>%
pull(Gene.ID)
cat("Number of downregulated DEGs:", length(down_degs), "\n")
Number of downregulated DEGs: 215
# Save the DEG gene list to disk:
write.table(down_degs, "down_DEGs.txt", row.names = FALSE, col.names = FALSE, quote = FALSE)
Now run gost as multi-query:
ora_multi <- gost(
query = list("upregulated" = up_degs, "downregulated" = down_degs),
organism = "hsapiens",
ordered_query = FALSE,
multi_query = TRUE,
significant = TRUE,
exclude_iea = FALSE,
measure_underrepresentation = FALSE,
evcodes = FALSE,
user_threshold = 0.05,
correction_method = "g_SCS",
domain_scope = "custom_annotated",
custom_bg = background,
numeric_ns = "",
sources = NULL,
as_short_link = FALSE,
highlight = FALSE
)
And create an interactive plot with gostplot:
gostplot(ora_multi, capped = TRUE, interactive = TRUE)
To access the tabular results separately, they need to be split. View
the output to see that the columns are comma separated, upregulated
result first (query 1 in our list) and down-regulated second (query
2).
— WHY DOES THIS WORK IN THE CONSOLE BUT NOT THE NOTEBOK??? — WHAT IS
A SANE WAY TO EXTRACT THESE RESULTS TO VIEW THE TABLE
head(ora_multi$result)
8. Compare gprofiler2 R results to the g:Profiler web results
In day 1 of the workshop, you ran ORA with g:Profiler web tool and
saved the results to a CSV. Let’s compare the results to those we have
generated in R. Do we expect the results to be identical or differ
slightly?
# ensure the filepath matches yours. Note the "../" means one directory level below this one
web <- read.csv("../day_1/gProfiler_hsapiens_07-11-2024_11-27-09__intersections.csv")
Check the numbers: are there any terms significant frm one tool but
not the other?
# Extract significant term names
web_terms <- web$term_name
ora_terms <- ora$result$term_name
paste0("Number of significant terms from web: ", length(web_terms))
paste0("Number of significant terms from R: ", length(ora_terms))
# Find command and unique terms
common_terms <- intersect(web_terms, ora_terms)
if (length(common_terms) == length(web_terms) && length(common_terms) == length(ora_terms)) {
# If the lengths match, all terms are shared
print("All terms are shared")
} else {
# If there are differences, report the number of terms
unique_web <- setdiff(web_terms, ora_terms)
unique_ora <- setdiff(ora_terms, web_terms)
print(paste("Number of terms unique to web:", length(unique_web)))
print(paste("Number of terms unique to gprofiler2 (R):", length(unique_ora)))
}
That’s a good start! Do the P values differ by much? Let’s look
closely at GO ‘Molecular Function’.
# Filter for GO:MF terms
go_mf_web <- web %>% filter(source == "GO:MF")
go_mf_r <- ora$result %>% filter(source == "GO:MF")
# Extract term names and p values
comparison_data_go_mf <- data.frame(
term_name = go_mf_web$term_name,
p_value_web = go_mf_web$adjusted_p_value,
p_value_r = go_mf_r$p_value
)
# Reshape the data to long format
comparison_data_long <- comparison_data_go_mf %>%
pivot_longer(cols = starts_with("p_value"),
names_to = "source",
values_to = "p_value")
# Create the bar plot with -log10 transformed p-values
print(ggplot(comparison_data_long, aes(x = term_name, y = -log10(p_value), fill = source)) +
geom_bar(stat = "identity", position = "dodge", width = 0.7) + # Side-by-side bars
labs(title = "Adjusted P value comparison for GO:MF enrichments",
x = "Term Name",
y = "-log10(P-value)") +
scale_fill_manual(values = c("p_value_web" = "#ff9900", "p_value_r" = "#3366cc")) + # Custom colors
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) )
Great! We know we applied the same parameters, and used the same
input gene lists. The identical results must mean that gprofiler2 is
using the same database version as g:Profiler web.
Saving versions and session details
Let’s check: yesterday when you ran ORA on the web, hopefully you
saved your ‘query parameters’ as well as your results.
From my run, I can see version as ‘e111_eg58_p18_f463989d’.
Let’s report the g:Profiler database version used in our
analysis:
paste0("g:Profiler database version: ", ora$meta$version)
paste0("gprofiler2 package version: ", packageVersion("gprofiler2"))
We can also capture the version of R and other session details
including all loaded packages and versions with the
sessionInfo() function:
sessionInfo()
R version 4.4.2 (2024-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22631)
Matrix products: default
locale:
[1] LC_COLLATE=English_Australia.utf8 LC_CTYPE=English_Australia.utf8 LC_MONETARY=English_Australia.utf8
[4] LC_NUMERIC=C LC_TIME=English_Australia.utf8
time zone: Australia/Sydney
tzcode source: internal
attached base packages:
[1] stats4 stats graphics grDevices utils datasets methods base
other attached packages:
[1] tidyr_1.3.1 gprofiler2_0.2.3 ggplot2_3.5.1 biomaRt_2.62.0 DOSE_4.0.0
[6] enrichplot_1.26.2 clusterProfiler_4.14.0 org.Hs.eg.db_3.20.0 AnnotationDbi_1.68.0 IRanges_2.40.0
[11] S4Vectors_0.44.0 Biobase_2.66.0 BiocGenerics_0.52.0 ReactomePA_1.50.0 dplyr_1.1.4
[16] readr_2.1.5 WebGestaltR_0.4.6
loaded via a namespace (and not attached):
[1] RColorBrewer_1.1-3 rstudioapi_0.17.1 jsonlite_1.8.9 magrittr_2.0.3 ggtangle_0.0.4
[6] farver_2.1.2 rmarkdown_2.29 fs_1.6.5 zlibbioc_1.52.0 vctrs_0.6.5
[11] memoise_2.0.1 RCurl_1.98-1.16 ggtree_3.14.0 progress_1.2.3 htmltools_0.5.8.1
[16] curl_5.2.3 gridGraphics_0.5-1 sass_0.4.9 bslib_0.8.0 htmlwidgets_1.6.4
[21] httr2_1.0.6 plyr_1.8.9 plotly_4.10.4 cachem_1.1.0 whisker_0.4.1
[26] igraph_2.1.1 lifecycle_1.0.4 iterators_1.0.14 pkgconfig_2.0.3 Matrix_1.7-1
[31] R6_2.5.1 fastmap_1.2.0 gson_0.1.0 GenomeInfoDbData_1.2.13 digest_0.6.37
[36] aplot_0.2.3 ggnewscale_0.5.0 colorspace_2.1-1 patchwork_1.3.0 crosstalk_1.2.1
[41] RSQLite_2.3.7 labeling_0.4.3 filelock_1.0.3 fansi_1.0.6 httr_1.4.7
[46] polyclip_1.10-7 compiler_4.4.2 rngtools_1.5.2 bit64_4.5.2 withr_3.0.2
[51] doParallel_1.0.17 graphite_1.52.0 BiocParallel_1.40.0 viridis_0.6.5 DBI_1.2.3
[56] ggupset_0.4.0 apcluster_1.4.13 ggforce_0.4.2 R.utils_2.12.3 MASS_7.3-61
[61] rappdirs_0.3.3 tools_4.4.2 ape_5.8 R.oo_1.27.0 glue_1.8.0
[66] nlme_3.1-166 GOSemSim_2.32.0 grid_4.4.2 reshape2_1.4.4 snow_0.4-4
[71] fgsea_1.32.0 generics_0.1.3 gtable_0.3.6 tzdb_0.4.0 R.methodsS3_1.8.2
[76] data.table_1.16.2 hms_1.1.3 xml2_1.3.6 tidygraph_1.3.1 utf8_1.2.4
[81] XVector_0.46.0 ggrepel_0.9.6 foreach_1.5.2 pillar_1.9.0 stringr_1.5.1
[86] yulab.utils_0.1.8 vroom_1.6.5 splines_4.4.2 tweenr_2.0.3 BiocFileCache_2.14.0
[91] treeio_1.30.0 lattice_0.22-6 bit_4.5.0 tidyselect_1.2.1 GO.db_3.20.0
[96] Biostrings_2.74.0 reactome.db_1.89.0 knitr_1.49 gridExtra_2.3 svglite_2.1.3
[101] xfun_0.49 graphlayouts_1.2.0 stringi_1.8.4 UCSC.utils_1.2.0 lazyeval_0.2.2
[106] ggfun_0.1.7 yaml_2.3.10 evaluate_1.0.1 codetools_0.2-20 ggraph_2.2.1
[111] tibble_3.2.1 qvalue_2.38.0 BiocManager_1.30.25 graph_1.84.0 ggplotify_0.1.2
[116] cli_3.6.3 systemfonts_1.1.0 munsell_0.5.1 jquerylib_0.1.4 Rcpp_1.0.13-1
[121] GenomeInfoDb_1.42.0 dbplyr_2.5.0 png_0.1-8 parallel_4.4.2 blob_1.2.4
[126] prettyunits_1.2.0 doRNG_1.8.6 bitops_1.0-9 viridisLite_0.4.2 tidytree_0.4.6
[131] ggridges_0.5.6 scales_1.3.0 purrr_1.0.2 crayon_1.5.3 rlang_1.1.4
[136] cowplot_1.1.3 fastmatch_1.1-4 KEGGREST_1.46.0
And finally, the RStudio verson:
RStudio.Version()
$citation
To cite RStudio in publications use:
Posit team (2024). RStudio: Integrated Development Environment for R. Posit Software, PBC, Boston, MA. URL
http://www.posit.co/.
A BibTeX entry for LaTeX users is
@Manual{,
title = {RStudio: Integrated Development Environment for R},
author = {{Posit team}},
organization = {Posit Software, PBC},
address = {Boston, MA},
year = {2024},
url = {http://www.posit.co/},
}
$mode
[1] "desktop"
$version
[1] ‘2024.9.1.394’
$long_version
[1] "2024.09.1+394"
$release_name
[1] "Cranberry Hibiscus"
End of activity summary
- We have extracted a gene list and background gene list from a DE
dataset and run ORA with
gprofiler2 gost
function
- We have plotted the data with
gostplot Manhattan plots
and ggplot2 dotplots
- We have run a
gost multi-query separating up and down
regulated genes
- We have verified that
gprofiler2 results match the
results from g:Profiler web
- We have captured all version details relevant to the session within
the R notebook
The last task is to knit the notebook. Our notebook is
editable, and can be changed. Deleting code deletes the output, so we
could lose valuable details. If we knit the notebook to HTML, we have a
permanent static copy of the work.
LS0tDQp0aXRsZTogIk9SQSB3aXRoIGdwcm9maWxlcjIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyIExvYWQgUiBwYWNrYWdlcywgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShncHJvZmlsZXIyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh0aWR5cikNCg0KYGBgDQoNCg0KW2BncHJvZmlsZXIyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dwcm9maWxlcjIvaW5kZXguaHRtbCkgaXMgdGhlIFIgaW50ZXJmYWNlIHRvIHRoZSBbYGc6UHJvZmlsZXJgXShodHRwczovL2JpaXQuY3MudXQuZWUvZ3Byb2ZpbGVyL2dvc3QpIHRvb2xzZXQuIA0KDQpMaWtlIHRoZSB3ZWIgaW50ZXJmYWNlLCBncHJvZmlsZXIyIHBlcmZvcm1zIE9SQSB3aXRoIGBnOkdPU3RgIGFnYWluc3QgbXVsdGlwbGUgZGF0YWJhc2VzIHNpbXVsdGFuZW91c2x5LiBJdCBzdXBwb3J0cyBtdWx0aXBsZSBuYW1lc3BhY2VzIGFuZCBudW1lcm91cyBzcGVjaWVzLiANCg0KIyAxLiBMb2FkIGlucHV0IGRhdGENCg0KVGhlIGRhdGFzZXQgY29tcHJpc2VzIDIgZXhwZXJpbWVudGFsIGdyb3VwcyAoZGlmZmVyZW50aWF0ZWQgYW5kIHVuZGlmZmVyZW50aWF0ZWQgY2VsbHMgaW4gcmVzcG9uc2UgdG8gIHRyZWF0bWVudCkgd2l0aCAzIHJlcGxpY2F0ZXMgaW4gZWFjaCBncm91cC4gVGhlIHJhdyBkYXRhIGZyb20gW1BlenppbmkgZXQgYWwgMjAxNl0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9hcnRpY2xlLzEwLjEwMDcvczEwNTcxLTAxNi0wNDAzLXkpIHdhcyBzdWJqZWN0ZWQgdG8gZGlmZmVyZW50aWFsIGdlbmUgZXhwcmVzc2lvbiBhbmFseXNpcyB3aXRoIFtEZWd1c3RdKGh0dHBzOi8vZGVndXN0LmVyYy5tb25hc2guZWR1LykgYW5kIHRoZSByZXN1bHRzIGZpbGUgc2F2ZWQuIA0KDQpZb3Ugd2lsbCBmaW5kIGEgY29weSBvZiB0aGUgREUgcmVzdWx0cyBgUGV6emluaV9ERS50eHRgIHdpdGhpbiB0aGUgYEZ1bmN0aW9uYWxfZW5yaWNobWVudF93b3Jrc2hvcF8yMDI0L2RheV8yYCBmb2xkZXIuIA0KDQpGaXJzdCwgZW5zdXJlIHRoYXQgeW91ciB3b3JraW5nIGRpcmVjdG9yeSByZWZsZWN0cyB0aGlzIGRheV8yIGZvbGRlcjoNCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgU2V0IHRoZSByb290IGRpcmVjdG9yeSBmb3IgYWxsIG5vdGVib29rIGNodW5rcw0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAiQzovVXNlcnMvY3dpbDIyODEvT25lRHJpdmUgLSBUaGUgVW5pdmVyc2l0eSBvZiBTeWRuZXkgKFN0YWZmKS9Eb2N1bWVudHMvUElQRS01MTE5LWVucmljaG1lbnQtd29ya3Nob3AvZGF5XzIiKQ0KDQojIGFuZCBzZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5IGZvciB0aGUgY29uc29sZToNCnNldHdkKCJDOi9Vc2Vycy9jd2lsMjI4MS9PbmVEcml2ZSAtIFRoZSBVbml2ZXJzaXR5IG9mIFN5ZG5leSAoU3RhZmYpL0RvY3VtZW50cy9QSVBFLTUxMTktZW5yaWNobWVudC13b3Jrc2hvcC9kYXlfMiIpDQoNCg0KZ2V0d2QoKQ0KDQpgYGANCg0KDQoNClBsZWFzZSBhc2sgZm9yIGFzc2lzdGFuY2UgaWYgdGhpcyBzdGVwIGRvZXMgbm90IHNob3cgdGhlIGRheV8yIGZvbGRlciBwYXRoISANCg0KT25jZSB5b3VyIHdvcmtpbmcgZGlyZWN0b3J5IGlzIHNldCwgbG9hZCB0aGUgREUgaW5wdXQgZmlsZSwgYW5kIGluc3BlY3QgdGhlIGZpcnN0IGZldyBsaW5lczogDQoNCg0KYGBge3IgbG9hZCBpbnB1dCBkYXRhfQ0KZGF0YSA8LSByZWFkX3RzdigiUGV6emluaV9ERS50eHQiLCBjb2xfbmFtZXMgPSBUUlVFLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQ0KaGVhZChkYXRhKQ0KYGBgDQoNClRoZSBkYXRhZnJhbWUgc2hvd3MgZ2VuZXMgd2l0aCBmb2xkIGNoYW5nZSBhbmQgRkRSIHZhbHVlcywgYWxvbmcgd2l0aCBzb21lIG5vcm1hbGlzZWQgY291bnRzIHZhbHVlcyBmb3IgdGhlIDYgc2FtcGxlcy4gDQoNCkxvb2sgb24gdGhlIGVudmlyb25tZW50IHBhbmUgb2YgUlN0dWRpbywgYW5kIHlvdSBjYW4gc2VlIGEgZGVzY3JpcHRpb24gJzE0NDIwIG9icy4gb2YgMTAgdmFyaWFibGVzJyAtIHRoaXMgc2hvd3MgeW91ciBkYXRhZnJhbWUgY29uc2lzdHMgb2YgMTAgY29sdW1ucyBhbmQgMTQsNDIwIGdlbmVzLiANCg0KIyAyLiBHZXQgT1JBIGdlbmUgbGlzdA0KDQpOb3cgd2UgbmVlZCB0byBmaWx0ZXIgZm9yIERFR3MsIGFuZCB3ZSB3aWxsIGFwcGx5IHRoZSBzYW1lIHRocmVzaG9sZHMgYXMgYXBwbGllZCBpbiBkYXkgMTogYWRqdXN0ZWQgUCB2YWx1ZXMvRkRSIDwgMC4wMSwgYW5kIGxvZzJmb2xkIGNoYW5nZSBvZiAyLiANCg0KYGBge3IgZ2V0IE9SQSBnZW5lIGxpc3R9DQpkZWdzIDwtIGRhdGEgJT4lDQogIGZpbHRlcihGRFIgPCAwLjAxICYgYWJzKExvZzJGQykgPiAyKSAlPiUNCiAgcHVsbChHZW5lLklEKQ0KY2F0KCJOdW1iZXIgb2YgZ2VuZXMgcGFzc2luZyBGRFIgYW5kIGZvbGQgY2hhbmdlIGZpbHRlcjoiLCBsZW5ndGgoZGVncyksICJcbiIpDQoNCiMgU2F2ZSB0aGUgREVHIGdlbmUgbGlzdCB0byBkaXNrOiANCndyaXRlLnRhYmxlKGRlZ3MsICJERUdzLnR4dCIsIHJvdy5uYW1lcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBGQUxTRSwgcXVvdGUgPSBGQUxTRSkNCg0KYGBgDQpXZSBoYXZlIDc5MiBnZW5lcyBwYXNzaW5nIG91ciBmaWx0ZXJzLiANCg0KIyAzLiBHZXQgYmFja2dyb3VuZCBnZW5lIGxpc3QNCg0KUmVjYWxsIGZyb20gdGhlIHdlYmluYXIgYW5kIGRheSAxIG9mIHRoZSB3b3Jrc2hvcCB0aGF0IGFuIGV4cGVyaW1lbnRhbCBiYWNrZ3JvdW5kIGdlbmUgbGlzdCBpcyBjcnVjaWFsIHRvIGF2b2lkaW5nIGZhbHNlIHBvc2l0aXZlcyBhbmQgbWluaW1pc2luZyB0aXNzdWUgYmlhcy4gDQoNClRoZSBhbmFseXNpcyBpbiBEZWd1c3QgaGFzIGFscmVhZHkgcmVtb3ZlZCBsb3dseSBleHByZXNzZWQgZ2VuZXMsIHNvIHdlIGNhbiBzaW1wbHkgZXh0cmFjdCBhbGwgZ2VuZXMgZnJvbSB0aGlzIGRhdGEgbWF0cml4IGFzIG91ciBiYWNrZ3JvdW5kIGdlbmUgbGlzdCBhbmQgc2F2ZSBpdCBhcyBvdXIgJ2JhY2tncm91bmQnIG9iamVjdC4gDQoNCmBgYHtyIGdldCBiYWNrZ3JvdW5kIH0NCmJhY2tncm91bmQgPC0gZGF0YSRHZW5lLklEDQpjYXQoIk51bWJlciBvZiBiYWNrZ3JvdW5kIGdlbmVzOiIsIGxlbmd0aChiYWNrZ3JvdW5kKSwgIlxuIikNCg0KIyBTYXZlIHRoZSBiYWNrZ3JvdW5kIGdlbmUgbGlzdCB0byBkaXNrOg0Kd3JpdGUudGFibGUoYmFja2dyb3VuZCwgImJhY2tncm91bmRfZ2VuZXMudHh0Iiwgcm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQ0KYGBgDQoNCiMgNC4gUnVuIE9SQSB3aXRoIGBnb3N0YCBmdW5jdGlvbg0KDQpCZWZvcmUgcnVubmluZyB0aGUgYmVsb3cgY29kZSBjaHVuaywgcmV2aWV3IHRoZSBwYXJhbWV0ZXJzLiBPYnNlcnZlIHRoZSBzaW1pbGFyaXRpZXMgdG8gd2hhdCB3YXMgYXZhaWxhYmxlIG9uIHRoZSBnOlByb2ZpbGVyIHdlYiBpbnRlcmZhY2UsIGZvciBleGFtcGxlIG9yZ2FuaXNtLCB0aGUgY29ycmVjdGlvbiBtZXRob2QgKGc6UHJvZmlsZXIncyBjdXN0b20gYGdfc2NzYCBtZXRob2QpLCBhbmQgZG9tYWluIHNjb3BlLiANCg0KRXZlcnkgUiBwYWNrYWdlIGhhcyBhIHVzZXIgZ3VpZGUgdGhhdCBvdXRsaW5lcyBhdmFpbGFibGUgcGFyYW1ldGVycy4gT3BlbiB0aGUgW2dwcm9maWxlcjJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9ncHJvZmlsZXIyL2dwcm9maWxlcjIucGRmKSB1c2VyIGd1aWRlIGFuZCBuYXZpZ2F0ZSB0byBwYWdlIGRldGFpbGluZyB0aGUgYGdvc3RgIGZ1bmN0aW9uIChjdXJyZW50bHkgcGFnZSA2KS4gDQoNClVuZGVyIHRoZSBgVXNhZ2VgIHN1YmhlYWRpbmcsIGFsbCBhdmFpbGFibGUgIHBhcmFtZXRlcnMgYXJlIGxpc3RlZCwgYWxvbmcgd2l0aCB0aGVpciBkZWZhdWx0IHNldHRpbmdzLiAgWW91IGNhbiBvdmVyLXJpZGUgdGhlc2UgcGFyYW1ldGVycyBieSBzcGVjaWZ5aW5nIHlvdXIgY3VzdG9tIHZhbHVlcyBpbiB5b3VyIFIgY29tbWFuZC4gDQoNClRoZSBiZWxvdyBmdWxsIGNvZGUgc2hvd3MgYWxsIHBhcmFtZXRlcnM6IA0KDQpgYGB7ciBydW4gZ29zdH0NCm9yYSA8LSBnb3N0KCANCiAgICBkZWdzLCANCiAgICBvcmdhbmlzbSA9ICJoc2FwaWVucyIsIA0KICAgIG9yZGVyZWRfcXVlcnkgPSBGQUxTRSwgDQogICAgbXVsdGlfcXVlcnkgPSBGQUxTRSwgDQogICAgc2lnbmlmaWNhbnQgPSBUUlVFLCANCiAgICBleGNsdWRlX2llYSA9IEZBTFNFLCANCiAgICBtZWFzdXJlX3VuZGVycmVwcmVzZW50YXRpb24gPSBGQUxTRSwgDQogICAgZXZjb2RlcyA9IEZBTFNFLCANCiAgICB1c2VyX3RocmVzaG9sZCA9IDAuMDUsIA0KICAgIGNvcnJlY3Rpb25fbWV0aG9kID0gImdfU0NTIiwgDQogICAgZG9tYWluX3Njb3BlID0gImN1c3RvbV9hbm5vdGF0ZWQiLCANCiAgICBjdXN0b21fYmcgPSBiYWNrZ3JvdW5kLCANCiAgICBudW1lcmljX25zID0gIiIsIA0KICAgIHNvdXJjZXMgPSBOVUxMLCANCiAgICBhc19zaG9ydF9saW5rID0gRkFMU0UsIA0KICAgIGhpZ2hsaWdodCA9IEZBTFNFIA0KKQ0KYGBgDQoNClNpbmNlIHdlIGFyZSB1c2luZyBtYW55IG9mIHRoZSBkZWZhdWx0IHBhcmFtZXRlcnMsIHdlIGNvdWxkIHNob3J0ZW4gdGhpcyBjb2RlIHRvIHdoYXQgaXMgc2hvd24gYmVsb3cuIFRoZSByZXN1bHRzIHdvdWxkIGJlIGlkZW50aWNhbCwgaG93ZXZlciBub3QgYXMgdHJhbnNwYXJlbnQgYXMgZmFyIGFzIGVhc2lseSBpZGVudGlmeWluZyB3aGF0IHBhcmFtZXRlcnMgd2VyZSBhcHBsaWVkIHRvIGEgcnVuLiANCg0KVGlwOiB5b3UgY2FuIHJ1biB0aGUgY29tbWFuZCBgZm9ybWFscyhnb3N0KWAgdG8gcHJpbnQgb3V0IGFsbCBwYXJhbWV0ZXJzIGFuZCB0aGVpciBkZWZhdWx0IHZhbHVlcy4gVGhpcyBpcyBhIGdlbmVyaWMgUiBmdW5jdGlvbiBzbyBjYW4gYmUgdXNlZCBvbiBvdGhlciB0b29scyBvdXRzaWRlIG9mIGBncHJvZmlsZXIyYC4gDQoNCg0KYGBge3IgYWJicmV2aWF0ZWQgZ29zdCBjb2RlIH0NCg0KI29yYSA8LSBnb3N0KCANCiMgICAgZGVncywgDQojICAgIGNvcnJlY3Rpb25fbWV0aG9kID0gImdfU0NTIiwgDQojICAgIGRvbWFpbl9zY29wZSA9ICJjdXN0b21fYW5ub3RhdGVkIiwNCiMgICAgY3VzdG9tX2JnID0gYmFja2dyb3VuZCwNCiMpDQoNCmBgYA0KDQpgYGB7ciBwcmludCBkZWZhdWx0c30NCmZvcm1hbHMoZ29zdCkNCmBgYA0KDQoNCg0KV2UgaGF2ZSBub3cgcnVuIE9SQSB3aXRoIGdwcm9maWxlcjIhIFZpZXcgdGhlIHRvLW1vc3Qgc2lnbmlmaWNhbnQgZW5yaWNobWVudHMgd2l0aCB0aGUgUiBgaGVhZGAgY29tbWFuZC4gT25seSBzaWduaWZpY2FudCBlbnJpY2htZW50cyBwYXNzaW5nIHlvdXIgc3BlY2lmaWVkIHRocmVzaG9sZCBhcmUgaW5jbHVkZWQgaW4gdGhlIHJlc3VsdHMgb2JqZWN0IHdlIGhhdmUgbmFtZWQgYG9yYWAuIA0KDQoNCmBgYHtyIGhlYWQgb3JhfQ0KaGVhZChvcmEkcmVzdWx0KQ0KYGBgDQoNCg0KVGhlc2UgYXJlIGFsbCBiaW9sb2dpY2FsIHByb2Nlc3MuIFdoYXQgZGF0YWJhc2VzIGRpZCB0aGlzIHJ1biBPUkEgYWdhaW5zdD8NCg0KDQpgYGB7ciBnb3N0IGRic30NCnVuaXF1ZShvcmEkcmVzdWx0JHNvdXJjZSkNCmBgYA0KDQoNClNhbWUgYXMgdGhlIHdlYiB0b29sLCB3ZSBoYXZlIGVucmljaG1lbnQgcmVzdWx0cyBmb3IgR1AgKEJQLCBDQywgTUYpLCBIUCAoaHVtYW4gcGhlbm90eXBlKSwgSFBBIChodW1hbiBwcm90ZWluIGF0bGFzKSwgS0VHRywgTWlSTkEsIFJlYWN0b21lLCBUcmFuc2NyaXB0aW9uIEZhY3RvcnMgYW5kIFdpa2lQYXRod2F5cy4gDQoNCg0KDQojIDUuIFNhdmUgdGhlIHJlc3VsdHMgdG8gYSBmaWxlDQoNClRoZSByZXN1bHRzIGNhbiBiZSBzYXZlZCB0byBkaXNrIHRvIHNpbXBsaWZ5IHNoYXJpbmcgb3IgbG9jYWwgdmlld2luZyBvdXRzaWRlIHRoZSBSIGVudmlyb25tZW50LiBUaGUgb3V0cHV0IGNhbiBiZSByZS1vcmRlcmVkIHRvIG1vcmUgY2xvc2VseSBtYXRjaCB3aGF0IGlzIHByb2R1Y2VkIGluIHRoZSB3ZWIgdG9vbDogDQoNCg0KYGBge3Igd3JpdGUgb3JhIHRhYmxlfQ0KDQpvcmFfcmVvcmRlcmVkIDwtIG9yYSRyZXN1bHRbLCBjKCJzb3VyY2UiLCAidGVybV9uYW1lIiwgInRlcm1faWQiLCAicF92YWx1ZSIsICJ0ZXJtX3NpemUiLCAicXVlcnlfc2l6ZSIsICJpbnRlcnNlY3Rpb25fc2l6ZSIsICJlZmZlY3RpdmVfZG9tYWluX3NpemUiKV0NCg0KaGVhZChvcmFfcmVvcmRlcmVkKQ0KDQp3cml0ZS5jc3Yob3JhX3Jlb3JkZXJlZCwgImdwcm9maWxlcl9PUkFfcmVzdWx0cy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCg0KDQpgYGANCg0KDQojIDYuIFZpc3VhbGlzZSB0aGUgcmVzdWx0cyANCg0KIyMgTWFuaGF0dGFuIHBsb3RzIA0KDQpUaGUgYGdvc3RwbG90YCBmdW5jdGlvbiBjcmVhdGVzIGEgTWFuaGF0dGFuIHBsb3Qgc2ltaWxhciB0byB0aGUgb25lIHNob3duIG9uIHRoZSB3ZWIgdG9vbC4gQnkgc2V0dGluZyBgaW50ZXJhY3RpdmU9VFJVRWAgd2UgY2FuIGhvdmVyIG92ZXIgdGhlIGRhdGEgcG9pbnRzIHRvIHNlZSBlbnJpY2hlZCB0ZXJtIGRldGFpbHMuIA0KDQoNCmBgYHtyIGdvc3RwbG90fQ0KDQpnb3N0cGxvdChvcmEsIA0KICBjYXBwZWQgPSBUUlVFLCANCiAgaW50ZXJhY3RpdmUgPSBUUlVFLCANCiAgcGFsID0gYyhgR086TUZgID0gIiNkYzM5MTIiLCANCiAgICBgR086QlBgID0gIiNmZjk5MDAiLCANCiAgICBgR086Q0NgID0gIiMxMDk2MTgiLCANCiAgICBLRUdHID0gIiNkZDQ0NzciLCANCiAgICBSRUFDID0gIiMzMzY2Y2MiLCANCiAgICBXUCA9ICIjMDA5OWM2IiwgDQogICAgVEYgPSAiIzU1NzRhNiIsIA0KICAgIE1JUk5BID0gIiMyMmFhOTkiLCANCiAgICBIUEEgPSAiIzY2MzNjYyIsIA0KICAgIENPUlVNID0gIiM2NmFhMDAiLCANCiAgICBIUCA9ICIjOTkwMDk5IikgDQopDQoNCmBgYA0KDQoNClRoZXJlIGFyZSBhIGxvdCBvZiBzaWduaWZpY2FudCBlbnJpY2htZW50cyBmb3IgR08gYmlvbG9naWNhbCBwcm9jZXNzZXMuIE1hbnkgb2YgdGhlc2UgYXJlIHByb2JhYmx5IHRlcm1zIGNvbnRhaW5pbmcgYSBsYXJnZSBudW1iZXIgb2YgZ2VuZXMsIHNvIG5vdCBwYXJ0aWN1bGFybHkgaW5mb3JtYXRpdmUuIFNpbmNlIHRoZXJlIGlzIG5vIGRpcmVjdCBwYXJhbWV0ZXIgdG8gcmVzdHJpY3QgdGVybSBzaXplIHRvIGBnb3N0cGxvdGAsIHdlIGNhbiBmaWx0ZXIgdGhlIE9SQSByZXN1bHRzIGJlZm9yZSBwbG90dGluZy4NCiANCg0KYGBge3IgZ29zdHBsb3QgLSBmaWx0ZXJlZCB0ZXJtIHNpemV9DQojIEZpbHRlciB0aGUgcmVzdWx0cyBmb3IgR086QlAgdGVybXMgd2l0aCB0ZXJtX3NpemUgPD0gNTAwDQpvcmFfZmlsdGVyX3Rlcm1zaXplIDwtIG9yYQ0Kb3JhX2ZpbHRlcl90ZXJtc2l6ZSRyZXN1bHQgPC0gb3JhJHJlc3VsdCAlPiUgZmlsdGVyKHRlcm1fc2l6ZSA8PSA1MDApDQoNCiMgUGxvdCB3aXRoIGdvc3RwbG90IHVzaW5nIHRoZSBmaWx0ZXJlZCByZXN1bHRzDQpnb3N0cGxvdChvcmFfZmlsdGVyX3Rlcm1zaXplLCANCiAgY2FwcGVkID0gVFJVRSwgDQogIGludGVyYWN0aXZlID0gVFJVRSwgDQogIHBhbCA9IGMoDQogICAgYEdPOk1GYCA9ICIjZGMzOTEyIiwgDQogICAgYEdPOkJQYCA9ICIjZmY5OTAwIiwgDQogICAgYEdPOkNDYCA9ICIjMTA5NjE4IiwgDQogICAgS0VHRyA9ICIjZGQ0NDc3IiwgDQogICAgUkVBQyA9ICIjMzM2NmNjIiwgDQogICAgV1AgPSAiIzAwOTljNiIsIA0KICAgIFRGID0gIiM1NTc0YTYiLCANCiAgICBNSVJOQSA9ICIjMjJhYTk5IiwgDQogICAgSFBBID0gIiM2NjMzY2MiLCANCiAgICBDT1JVTSA9ICIjNjZhYTAwIiwgDQogICAgSFAgPSAiIzk5MDA5OSINCiAgKQ0KKQ0KDQpgYGANCg0KDQpncHJvZmlsZXIyIGluY2x1ZGVzIGEgZnVuY3Rpb24gZm9yIGNyZWF0aW5nIGEgcHVibGljYXRpb24tcmVhZHkgaW1hZ2UgdGhhdCBjYW4gb3B0aW9uYWxseSBoaWdobGlnaHQgc3BlY2lmaWMgdGVybXMuIFdlIG5lZWQgdG8gZmlyc3QgcHJvZHVjZSBhIHBsb3Qgd2l0aCBgaW50ZXJhY3RpY2UgPSBGQUxTRWAsIHNhdmUgaXQgdG8gYW4gb2JqZWN0LCBhbmQgdGhlbiBwcm92aWRlIHRoYXQgcGxvdCBvYmplY3QgdG8gdGhlIGBwdWJsaXNoX2dvc3RwbG90YCBmdW5jdGlvbi4gDQoNCg0KYGBge3IgZ29zdHBsb3Qgbm9uLWludGVyYWN0aXZlfQ0KDQojIFBsb3Qgd2l0aCBnb3N0cGxvdCB1c2luZyB0aGUgZmlsdGVyZWQgcmVzdWx0cw0KcGxvdCA8LSBnb3N0cGxvdChvcmFfZmlsdGVyX3Rlcm1zaXplLCANCiAgY2FwcGVkID0gVFJVRSwgDQogIGludGVyYWN0aXZlID0gRkFMU0UsIA0KICBwYWwgPSBjKA0KICAgIGBHTzpNRmAgPSAiI2RjMzkxMiIsIA0KICAgIGBHTzpCUGAgPSAiI2ZmOTkwMCIsIA0KICAgIGBHTzpDQ2AgPSAiIzEwOTYxOCIsIA0KICAgIEtFR0cgPSAiI2RkNDQ3NyIsIA0KICAgIFJFQUMgPSAiIzMzNjZjYyIsIA0KICAgIFdQID0gIiMwMDk5YzYiLCANCiAgICBURiA9ICIjNTU3NGE2IiwgDQogICAgTUlSTkEgPSAiIzIyYWE5OSIsIA0KICAgIEhQQSA9ICIjNjYzM2NjIiwgDQogICAgQ09SVU0gPSAiIzY2YWEwMCIsIA0KICAgIEhQID0gIiM5OTAwOTkiDQogICkNCikNCmBgYA0KDQpUaGUgYHB1Ymxpc2hfZ29zdHBsb3RgIGZ1bmN0aW9uIGhhcyBhIGBoaWdobGlnaHRfdGVybXNgIHBhcmFtZXRlciB0aGF0IGVuYWJsZXMgeW91IHRvIGhpZ2hsaWdodCBzcGVjaWZpYyB0ZXJtcyBvbiB0aGUgcGxvdCwgd2l0aCBhIHRhYmxlIHNob3dpbmcgZW5yaWNobWVudCBkZXRhaWxzIGJlbG93IGZvciB0aG9zZSBoaWdobGlnaHRlZCB0ZXJtcy4gDQoNCkxldCdzIGhpZ2hsaWdodCBzb21lIHNlbGVjdGVkIHRlcm1zIG1hbnVhbGx5LiBZb3UgbmVlZCB0byBwcm92aWRlIHRoZSB0ZXJtIElEIG5vdCB0ZXJtIG5hbWUuIA0KDQpgYGB7ciBwdWJsaXNoIGdvc3RwbG90IC0gY29sbGFnZW4gfQ0KI1Rlcm0gSURzIGZvciAnQ29sbGFnZW4gZGVncmFkYXRpb24nIGFuZCAnQ29sbGFnZW4gZm9ybWF0aW9uJw0KaGlnaGxpZ2h0IDwtIGMoIlJFQUM6Ui1IU0EtMTQ0MjQ5MCIsICJSRUFDOlItSFNBLTE0NzQyOTAiKQ0KDQpmaWxlbmFtZSA8LSAiZ29zdHBsb3RfZmlsdGVyX3Rlcm1zaXplX2NvbGxhZ2VuLnBuZyINCg0KcHVibGlzaF9nb3N0cGxvdChwbG90LCANCiAgaGlnaGxpZ2h0X3Rlcm1zID0gaGlnaGxpZ2h0LCANCiAgZmlsZW5hbWUsIA0KICB3aWR0aCA9IDEwLCANCiAgaGVpZ2h0ID0gMTAgKQ0KDQpgYGANCg0KDQpZb3UgY2FuIHVzZSBSIGBncmVwbGAgZnVuY3Rpb24gdG8gc2VhcmNoIGZvciB0ZXJtcyB3aXRoIG5hbWVzIG1hdGNoaW5nIHNvbWUga2V5d29yZC4gTGV0J3MgaGlnaGxpZ2h0IGFsbCB0ZXJtcyByZWxhdGVkIHRvIHJlY2VwdG9yczoNCg0KYGBge3IgcHVibGlzaCBnb3N0cGxvdCAtIHJlY2VwdG9yc30NCg0KIyBGaWx0ZXIgdGVybXMgY29udGFpbmluZyAicmVjZXB0b3IiIGtleXdvcmQgYW5kIGNyZWF0ZSBhIGxpc3Qgb2YgdGhvc2UgdGVybSBJRHMNCmhpZ2hsaWdodCA8LSBvcmEkcmVzdWx0ICU+JSANCiAgZmlsdGVyKGdyZXBsKCJyZWNlcHRvciIsIHRlcm1fbmFtZSwgaWdub3JlLmNhc2UgPSBUUlVFKSkgJT4lIA0KICBwdWxsKHRlcm1faWQpIA0KDQpmaWxlbmFtZSA8LSAiZ29zdHBsb3RfZmlsdGVyX3Rlcm1zaXplX3JlY2VwdG9ycy5wbmciDQoNCnB1Ymxpc2hfZ29zdHBsb3QocGxvdCwgDQogIGhpZ2hsaWdodF90ZXJtcyA9IGhpZ2hsaWdodCwgDQogIGZpbGVuYW1lLCANCiAgd2lkdGggPSAxMCwgDQogIGhlaWdodCA9IDEwICkNCg0KYGBgDQoNCg0KDQojIyBEb3RwbG90cw0KDQpJZiB5b3Ugd2FudGVkIHRvIHBsb3QgZWFjaCBkYXRhYmFzZSBzZXBhcmF0ZWx5IGFzIGEgdHJhZGl0aW9uYWwgZG90cGxvdCwgeW91IGNhbiBsb29wIHRocm91Z2ggZWFjaCBkYXRhYmFzZSBhbmQgdXNlIHRoZSBSIHBhY2thZ2UgYGdncGxvdDJgIHRvIG1ha2UgYSBkb3RwbG90LiAgV2UgY2FuIGZpbHRlciB0aGUgcmVzdWx0cyBieSBkYXRhYmFzZSBhbmQgdGVybSBzaXplOg0KDQoNCmBgYHtyIG9yYSBkb3RwbG90cyB9DQojIExpc3Qgb2YgZGF0YWJhc2VzDQpkYnMgPC0gdW5pcXVlKG9yYSRyZXN1bHQkc291cmNlKQ0KDQojIExvb3AgdGhyb3VnaCBlYWNoIGRhdGFiYXNlIGFuZCBjcmVhdGUgYSBzZXBhcmF0ZSBwbG8gZm9yIGVhY2gNCmZvciAoZGIgaW4gZGJzKSB7DQogICNkYl9yZXN1bHRzIDwtIG9yYSRyZXN1bHQgDQogIGRiX3Jlc3VsdHMgPC0gb3JhJHJlc3VsdCAlPiUgZmlsdGVyKHNvdXJjZSA9PSBkYiwgdGVybV9zaXplID49IDEwLCB0ZXJtX3NpemUgPD0gNTAwKQ0KICANCiAgIyBDcmVhdGUgYSBkb3QgcGxvdCBmb3IgdGhlIGN1cnJlbnQgZGF0YWJhc2UNCiAgcCA8LSBnZ3Bsb3QoZGJfcmVzdWx0cywgYWVzKHggPSByZW9yZGVyKHRlcm1fbmFtZSwgLXBfdmFsdWUpLCB5ID0gLWxvZzEwKHBfdmFsdWUpKSkgKw0KICAgIGdlb21fcG9pbnQoYWVzKHNpemUgPSB0ZXJtX3NpemUsIGNvbG9yID0gc2lnbmlmaWNhbnQpKSArDQogICAgbGFicyh0aXRsZSA9IHBhc3RlKGRiKSwgDQogICAgICAgICB4ID0gIlRlcm0iLCANCiAgICAgICAgIHkgPSAiLWxvZzEwKHAtdmFsdWUpIikgKw0KICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgY29vcmRfZmxpcCgpICAjIEZsaXBzIHRoZSBjb29yZGluYXRlcyBmb3IgYmV0dGVyIHZpc2liaWxpdHkNCiAgDQogICMgUHJpbnQgdGhlIHBsb3QNCiAgcHJpbnQocCkNCn0NCmBgYA0KDQogDQpGb3IgcGxvdHMgd2l0aCBhIGxvdCBvZiBlbnJpY2hlZCB0ZXJtcywgdGhlIGRpc3BsYXkgd2l0aGluIHRoZSBub3RlYm9vayBpcyBsZXNzIHRoYW4gaWRlYWwuIFNhdmluZyB0aGUgcGxvdCB0byBhbiBpbWFnZSBmaWxlIGVuYWJsZXMgYmV0dGVyIHJlc29sdXRpb246IA0KDQoNCmBgYHtyIHNhdmUgZ28gYnAgcGxvdH0NCg0KIyBGaWx0ZXIgZm9yIHRoZSBHTzpCUCBkYXRhYmFzZQ0KZ29fYnBfcmVzdWx0cyA8LSBvcmEkcmVzdWx0ICU+JSBmaWx0ZXIoc291cmNlID09ICJHTzpCUCIsdGVybV9zaXplID49IDEwLCB0ZXJtX3NpemUgPD0gNTAwKQ0KDQojIENyZWF0ZSB0aGUgcGxvdCBmb3IgR086QlANCnBfZ29fYnAgPC0gZ2dwbG90KGdvX2JwX3Jlc3VsdHMsIGFlcyh4ID0gcmVvcmRlcih0ZXJtX25hbWUsIC1wX3ZhbHVlKSwgeSA9IC1sb2cxMChwX3ZhbHVlKSkpICsNCiAgZ2VvbV9wb2ludChhZXMoc2l6ZSA9IHRlcm1fc2l6ZSwgY29sb3IgPSBzaWduaWZpY2FudCkpICsNCiAgbGFicyh0aXRsZSA9ICJHTzpCUCIsIA0KICAgICAgIHggPSAiVGVybSIsIA0KICAgICAgIHkgPSAiLWxvZzEwKHAtdmFsdWUpIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBjb29yZF9mbGlwKCkgICMgRmxpcHMgdGhlIGNvb3JkaW5hdGVzIGZvciBiZXR0ZXIgdmlzaWJpbGl0eQ0KDQoNCiMgT3BlbiBhIFBERiBkZXZpY2UgdG8gc2F2ZSB0aGUgcGxvdCBhcyBhIGZ1bGwgc2l6ZSBBNDogDQpwZGYoImdvX2JwX3Bsb3QucGRmIiwgd2lkdGggPSA4LjI3LCBoZWlnaHQgPSAxMS42OSkgICMgQTQgcG9ydHJhaXQgc2l6ZSBpbiBpbmNoZXMNCg0KIyBQcmludCB0aGUgcGxvdCB0byB0aGUgZGV2aWNlDQpwcmludChwX2dvX2JwKQ0KDQojIENsb3NlIHRoZSBkZXZpY2UgKHRoaXMgc2F2ZXMgdGhlIHBsb3QpDQpkZXYub2ZmKCkNCg0KYGBgDQoNCg0KDQoNCiMgNy4gUnVuIGEgZ29zdCBtdWx0aS1xdWVyeSBmb3IgdXAtcmVndWxhdGVkIGFuZCBkb3duLXJlZ3VsYXRlZCBnZW5lcywgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMgdG8gdGhvc2UgZ2VuZXJhdGVkIGZyb20gdGhlIGZ1bGwgREVHIGdlbmUgbGlzdA0KDQpCeSBwcm92aWRpbmcgbW9yZSB0aGFuIG9uZSBnZW5lIGxpc3QgYW5kIHNldHRpbmcgYG11bHRpX3F1ZXJ5ID0gVFJVRWAsIHJlc3VsdHMgZnJvbSBhbGwgb2YgdGhlIGdlbmUgbGlzdHMgYXJlIGdyb3VwZWQgYnkgdGVybSBJRHMgZm9yIGVhc2llciBjb21wYXJpc29uLg0KDQoNCkZpcnN0LCB3ZSBuZWVkIHRvIHJlLWV4dHJhY3Qgb3VyIGdlbmUgbGlzdCBzbyB3ZSBoYXZlIHNlcGFyYXRlIERFR3MgZm9yIHVwLXJlZ3VsYXRlZCBhbmQgZG93bi1yZWd1bGF0ZWQgZ2VuZXMuIA0KDQpgYGB7ciBnZXQgT1JBIGdlbmUgbGlzdHMgdXAgYW5kIGRvd24gfQ0KdXBfZGVncyA8LSBkYXRhICU+JQ0KICBmaWx0ZXIoRkRSIDwgMC4wMSAmIExvZzJGQyA+PSAyKSAlPiUNCiAgcHVsbChHZW5lLklEKQ0KY2F0KCJOdW1iZXIgb2YgdXByZWd1bGF0ZWQgREVHczoiLCBsZW5ndGgodXBfZGVncyksICJcbiIpDQoNCiMgU2F2ZSB0aGUgREVHIGdlbmUgbGlzdCB0byBkaXNrOiANCndyaXRlLnRhYmxlKHVwX2RlZ3MsICJ1cF9ERUdzLnR4dCIsIHJvdy5uYW1lcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBGQUxTRSwgcXVvdGUgPSBGQUxTRSkNCg0KZG93bl9kZWdzIDwtIGRhdGEgJT4lDQogIGZpbHRlcihGRFIgPCAwLjAxICYgTG9nMkZDIDw9IC0yKSAlPiUNCiAgcHVsbChHZW5lLklEKQ0KY2F0KCJOdW1iZXIgb2YgZG93bnJlZ3VsYXRlZCBERUdzOiIsIGxlbmd0aChkb3duX2RlZ3MpLCAiXG4iKQ0KDQojIFNhdmUgdGhlIERFRyBnZW5lIGxpc3QgdG8gZGlzazogDQp3cml0ZS50YWJsZShkb3duX2RlZ3MsICJkb3duX0RFR3MudHh0Iiwgcm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFKQ0KDQpgYGANCk5vdyBydW4gYGdvc3RgIGFzIG11bHRpLXF1ZXJ5OiANCg0KYGBge3IgcnVuIGdvc3QgbXVsdGktcXVlcnkgfQ0Kb3JhX211bHRpIDwtIGdvc3QoIA0KICAgIHF1ZXJ5ID0gbGlzdCgidXByZWd1bGF0ZWQiID0gdXBfZGVncywgImRvd25yZWd1bGF0ZWQiID0gZG93bl9kZWdzKSwgDQogICAgb3JnYW5pc20gPSAiaHNhcGllbnMiLCANCiAgICBvcmRlcmVkX3F1ZXJ5ID0gRkFMU0UsIA0KICAgIG11bHRpX3F1ZXJ5ID0gVFJVRSwgDQogICAgc2lnbmlmaWNhbnQgPSBUUlVFLCANCiAgICBleGNsdWRlX2llYSA9IEZBTFNFLCANCiAgICBtZWFzdXJlX3VuZGVycmVwcmVzZW50YXRpb24gPSBGQUxTRSwgDQogICAgZXZjb2RlcyA9IEZBTFNFLCANCiAgICB1c2VyX3RocmVzaG9sZCA9IDAuMDUsIA0KICAgIGNvcnJlY3Rpb25fbWV0aG9kID0gImdfU0NTIiwgDQogICAgZG9tYWluX3Njb3BlID0gImN1c3RvbV9hbm5vdGF0ZWQiLCANCiAgICBjdXN0b21fYmcgPSBiYWNrZ3JvdW5kLCANCiAgICBudW1lcmljX25zID0gIiIsIA0KICAgIHNvdXJjZXMgPSBOVUxMLCANCiAgICBhc19zaG9ydF9saW5rID0gRkFMU0UsIA0KICAgIGhpZ2hsaWdodCA9IEZBTFNFIA0KKQ0KYGBgDQoNCkFuZCBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgcGxvdCB3aXRoIGBnb3N0cGxvdGA6DQoNCmBgYHtyIGdvc3RwbG90IG11bHRpfQ0KZ29zdHBsb3Qob3JhX211bHRpLCBjYXBwZWQgPSBUUlVFLCBpbnRlcmFjdGl2ZSA9IFRSVUUpDQpgYGANCg0KVG8gYWNjZXNzIHRoZSB0YWJ1bGFyIHJlc3VsdHMgc2VwYXJhdGVseSwgdGhleSBuZWVkIHRvIGJlIHNwbGl0LiBWaWV3IHRoZSBvdXRwdXQgdG8gc2VlIHRoYXQgdGhlIGNvbHVtbnMgYXJlIGNvbW1hIHNlcGFyYXRlZCwgdXByZWd1bGF0ZWQgcmVzdWx0IGZpcnN0IChxdWVyeSAxIGluIG91ciBsaXN0KSBhbmQgZG93bi1yZWd1bGF0ZWQgc2Vjb25kIChxdWVyeSAyKS4gDQoNCg0KLS0tIFdIWSBET0VTIFRISVMgV09SSyBJTiBUSEUgQ09OU09MRSBCVVQgTk9UIFRIRSBOT1RFQk9LPz8/DQotLS0gV0hBVCBJUyBBIFNBTkUgV0FZIFRPIEVYVFJBQ1QgVEhFU0UgUkVTVUxUUyBUTyBWSUVXIFRIRSBUQUJMRQ0KDQpgYGB7cn0NCmhlYWQob3JhX211bHRpJHJlc3VsdCkNCmBgYA0KDQoNCiMgOC4gQ29tcGFyZSBncHJvZmlsZXIyIFIgcmVzdWx0cyB0byB0aGUgZzpQcm9maWxlciB3ZWIgcmVzdWx0cw0KDQpJbiBkYXkgMSBvZiB0aGUgd29ya3Nob3AsIHlvdSByYW4gT1JBIHdpdGggZzpQcm9maWxlciB3ZWIgdG9vbCBhbmQgc2F2ZWQgdGhlIHJlc3VsdHMgdG8gYSBDU1YuIExldCdzIGNvbXBhcmUgdGhlIHJlc3VsdHMgdG8gdGhvc2Ugd2UgaGF2ZSBnZW5lcmF0ZWQgaW4gUi4gRG8gd2UgZXhwZWN0IHRoZSByZXN1bHRzIHRvIGJlIGlkZW50aWNhbCBvciBkaWZmZXIgc2xpZ2h0bHk/IA0KDQoNCmBgYHtyIGltcG9ydCBnOnByb2ZpbGVyIHdlYiByZXN1bHRzfQ0KIyBlbnN1cmUgdGhlIGZpbGVwYXRoIG1hdGNoZXMgeW91cnMuIE5vdGUgdGhlICIuLi8iIG1lYW5zIG9uZSBkaXJlY3RvcnkgbGV2ZWwgYmVsb3cgdGhpcyBvbmUNCndlYiA8LSByZWFkLmNzdigiLi4vZGF5XzEvZ1Byb2ZpbGVyX2hzYXBpZW5zXzA3LTExLTIwMjRfMTEtMjctMDlfX2ludGVyc2VjdGlvbnMuY3N2IikNCmBgYA0KDQoNCkNoZWNrIHRoZSBudW1iZXJzOiBhcmUgdGhlcmUgYW55IHRlcm1zIHNpZ25pZmljYW50IGZybSBvbmUgdG9vbCBidXQgbm90IHRoZSBvdGhlcj8gDQoNCmBgYHtyfQ0KIyBFeHRyYWN0IHNpZ25pZmljYW50IHRlcm0gbmFtZXMNCndlYl90ZXJtcyA8LSB3ZWIkdGVybV9uYW1lDQpvcmFfdGVybXMgPC0gb3JhJHJlc3VsdCR0ZXJtX25hbWUNCg0KcGFzdGUwKCJOdW1iZXIgb2Ygc2lnbmlmaWNhbnQgdGVybXMgZnJvbSB3ZWI6ICIsIGxlbmd0aCh3ZWJfdGVybXMpKQ0KcGFzdGUwKCJOdW1iZXIgb2Ygc2lnbmlmaWNhbnQgdGVybXMgZnJvbSBSOiAiLCBsZW5ndGgob3JhX3Rlcm1zKSkNCg0KIyBGaW5kIGNvbW1hbmQgYW5kIHVuaXF1ZSB0ZXJtcw0KY29tbW9uX3Rlcm1zIDwtIGludGVyc2VjdCh3ZWJfdGVybXMsIG9yYV90ZXJtcykNCmlmIChsZW5ndGgoY29tbW9uX3Rlcm1zKSA9PSBsZW5ndGgod2ViX3Rlcm1zKSAmJiBsZW5ndGgoY29tbW9uX3Rlcm1zKSA9PSBsZW5ndGgob3JhX3Rlcm1zKSkgew0KICAjIElmIHRoZSBsZW5ndGhzIG1hdGNoLCBhbGwgdGVybXMgYXJlIHNoYXJlZA0KICBwcmludCgiQWxsIHRlcm1zIGFyZSBzaGFyZWQiKQ0KfSBlbHNlIHsNCiAgIyBJZiB0aGVyZSBhcmUgZGlmZmVyZW5jZXMsIHJlcG9ydCB0aGUgbnVtYmVyIG9mIHRlcm1zDQogIHVuaXF1ZV93ZWIgPC0gc2V0ZGlmZih3ZWJfdGVybXMsIG9yYV90ZXJtcykNCiAgdW5pcXVlX29yYSA8LSBzZXRkaWZmKG9yYV90ZXJtcywgd2ViX3Rlcm1zKQ0KICANCiAgcHJpbnQocGFzdGUoIk51bWJlciBvZiB0ZXJtcyB1bmlxdWUgdG8gd2ViOiIsIGxlbmd0aCh1bmlxdWVfd2ViKSkpDQogIHByaW50KHBhc3RlKCJOdW1iZXIgb2YgdGVybXMgdW5pcXVlIHRvIGdwcm9maWxlcjIgKFIpOiIsIGxlbmd0aCh1bmlxdWVfb3JhKSkpDQp9DQoNCmBgYA0KDQpUaGF0J3MgYSBnb29kIHN0YXJ0ISBEbyB0aGUgUCB2YWx1ZXMgZGlmZmVyIGJ5IG11Y2g/IExldCdzIGxvb2sgY2xvc2VseSBhdCBHTyAnTW9sZWN1bGFyIEZ1bmN0aW9uJy4gDQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQ0KDQojIEZpbHRlciBmb3IgR086TUYgdGVybXMNCmdvX21mX3dlYiA8LSB3ZWIgJT4lIGZpbHRlcihzb3VyY2UgPT0gIkdPOk1GIikNCmdvX21mX3IgPC0gb3JhJHJlc3VsdCAlPiUgZmlsdGVyKHNvdXJjZSA9PSAiR086TUYiKQ0KDQojIEV4dHJhY3QgdGVybSBuYW1lcyBhbmQgcCB2YWx1ZXMNCmNvbXBhcmlzb25fZGF0YV9nb19tZiA8LSBkYXRhLmZyYW1lKA0KICB0ZXJtX25hbWUgPSBnb19tZl93ZWIkdGVybV9uYW1lLA0KICBwX3ZhbHVlX3dlYiA9IGdvX21mX3dlYiRhZGp1c3RlZF9wX3ZhbHVlLA0KICBwX3ZhbHVlX3IgPSBnb19tZl9yJHBfdmFsdWUNCikNCg0KDQojIFJlc2hhcGUgdGhlIGRhdGEgdG8gbG9uZyBmb3JtYXQNCmNvbXBhcmlzb25fZGF0YV9sb25nIDwtIGNvbXBhcmlzb25fZGF0YV9nb19tZiAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgicF92YWx1ZSIpLCANCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInNvdXJjZSIsIA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInBfdmFsdWUiKQ0KDQojIENyZWF0ZSB0aGUgYmFyIHBsb3Qgd2l0aCAtbG9nMTAgdHJhbnNmb3JtZWQgcC12YWx1ZXMNCnByaW50KGdncGxvdChjb21wYXJpc29uX2RhdGFfbG9uZywgYWVzKHggPSB0ZXJtX25hbWUsIHkgPSAtbG9nMTAocF92YWx1ZSksIGZpbGwgPSBzb3VyY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC43KSArICAjIFNpZGUtYnktc2lkZSBiYXJzDQogIGxhYnModGl0bGUgPSAiQWRqdXN0ZWQgUCB2YWx1ZSBjb21wYXJpc29uIGZvciBHTzpNRiBlbnJpY2htZW50cyIsDQogICAgICAgeCA9ICJUZXJtIE5hbWUiLA0KICAgICAgIHkgPSAiLWxvZzEwKFAtdmFsdWUpIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJwX3ZhbHVlX3dlYiIgPSAiI2ZmOTkwMCIsICJwX3ZhbHVlX3IiID0gIiMzMzY2Y2MiKSkgKyAgIyBDdXN0b20gY29sb3JzDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICkgIA0KYGBgDQoNCkdyZWF0ISBXZSBrbm93IHdlIGFwcGxpZWQgdGhlIHNhbWUgcGFyYW1ldGVycywgYW5kIHVzZWQgdGhlIHNhbWUgaW5wdXQgZ2VuZSBsaXN0cy4gVGhlIGlkZW50aWNhbCByZXN1bHRzIG11c3QgbWVhbiB0aGF0IGdwcm9maWxlcjIgaXMgdXNpbmcgdGhlIHNhbWUgZGF0YWJhc2UgdmVyc2lvbiBhcyBnOlByb2ZpbGVyIHdlYi4gDQoNCiMgU2F2aW5nIHZlcnNpb25zIGFuZCBzZXNzaW9uIGRldGFpbHMgDQoNCkxldCdzIGNoZWNrOiB5ZXN0ZXJkYXkgd2hlbiB5b3UgcmFuIE9SQSBvbiB0aGUgd2ViLCBob3BlZnVsbHkgeW91IHNhdmVkIHlvdXIgJ3F1ZXJ5IHBhcmFtZXRlcnMnIGFzIHdlbGwgYXMgeW91ciByZXN1bHRzLiANCg0KRnJvbSBteSBydW4sIEkgY2FuIHNlZSB2ZXJzaW9uIGFzICdlMTExX2VnNThfcDE4X2Y0NjM5ODlkJy4gDQoNCkxldCdzIHJlcG9ydCB0aGUgZzpQcm9maWxlciBkYXRhYmFzZSB2ZXJzaW9uIHVzZWQgaW4gb3VyIGFuYWx5c2lzOg0KDQpgYGB7ciBncHJvZmlsZXIgdmVyc2lvbnN9DQoNCnBhc3RlMCgiZzpQcm9maWxlciBkYXRhYmFzZSB2ZXJzaW9uOiAiLCBvcmEkbWV0YSR2ZXJzaW9uKQ0KcGFzdGUwKCJncHJvZmlsZXIyIHBhY2thZ2UgdmVyc2lvbjogIiwgcGFja2FnZVZlcnNpb24oImdwcm9maWxlcjIiKSkNCg0KYGBgDQoNCldlIGNhbiBhbHNvIGNhcHR1cmUgdGhlIHZlcnNpb24gb2YgUiBhbmQgb3RoZXIgc2Vzc2lvbiBkZXRhaWxzIGluY2x1ZGluZyBhbGwgbG9hZGVkIHBhY2thZ2VzIGFuZCB2ZXJzaW9ucyB3aXRoIHRoZSBgc2Vzc2lvbkluZm8oKWAgZnVuY3Rpb246IA0KDQpgYGB7ciBpbmZvIH0NCnNlc3Npb25JbmZvKCkNCmBgYA0KQW5kIGZpbmFsbHksIHRoZSBSU3R1ZGlvIHZlcnNvbjogDQoNCmBgYHtyIHJzdHVkaW8gdn0NClJTdHVkaW8uVmVyc2lvbigpDQpgYGANCiMgRW5kIG9mIGFjdGl2aXR5IHN1bW1hcnkNCg0KLSBXZSBoYXZlIGV4dHJhY3RlZCBhIGdlbmUgbGlzdCBhbmQgYmFja2dyb3VuZCBnZW5lIGxpc3QgZnJvbSBhIERFIGRhdGFzZXQgYW5kIHJ1biBPUkEgd2l0aCBgZ3Byb2ZpbGVyMmAgYGdvc3RgIGZ1bmN0aW9uDQotIFdlIGhhdmUgcGxvdHRlZCB0aGUgZGF0YSB3aXRoIGBnb3N0cGxvdGAgTWFuaGF0dGFuIHBsb3RzIGFuZCBgZ2dwbG90MmAgZG90cGxvdHMNCi0gV2UgaGF2ZSBydW4gYSBgZ29zdGAgbXVsdGktcXVlcnkgc2VwYXJhdGluZyB1cCBhbmQgZG93biByZWd1bGF0ZWQgZ2VuZXMNCi0gV2UgaGF2ZSB2ZXJpZmllZCB0aGF0IGBncHJvZmlsZXIyYCByZXN1bHRzIG1hdGNoIHRoZSByZXN1bHRzIGZyb20gYGc6UHJvZmlsZXJgIHdlYg0KLSBXZSBoYXZlIGNhcHR1cmVkIGFsbCB2ZXJzaW9uIGRldGFpbHMgcmVsZXZhbnQgdG8gdGhlIHNlc3Npb24gd2l0aGluIHRoZSBSIG5vdGVib29rDQoNClRoZSBsYXN0IHRhc2sgaXMgdG8gYGtuaXRgIHRoZSBub3RlYm9vay4gT3VyIG5vdGVib29rIGlzIGVkaXRhYmxlLCBhbmQgY2FuIGJlIGNoYW5nZWQuIERlbGV0aW5nIGNvZGUgZGVsZXRlcyB0aGUgb3V0cHV0LCBzbyB3ZSBjb3VsZCBsb3NlIHZhbHVhYmxlIGRldGFpbHMuIElmIHdlIGtuaXQgdGhlIG5vdGVib29rIHRvIEhUTUwsIHdlIGhhdmUgYSBwZXJtYW5lbnQgc3RhdGljIGNvcHkgb2YgdGhlIHdvcmsuIA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K